﻿using System;
using System.Collections.Generic;
using DrawableObjects.Actors;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;
using Serialization.Script;
using Serialization;
using SpecialEffects;
using InterfaceComponents;
using DrawableObjects.Actors.Aircrafts;
using DrawableObjects.Actors.Projectiles;
using DrawableObjects.Actors.Aircrafts.Controller;
using DrawableObjects;
using Sirian.DrawableObject.Actors;
using GlobalGameClasses;
using System.Diagnostics;

namespace WorldView
{
    /// <summary>
    /// This class represents the world. It contains and controls all object
    /// of the gaming logic.
    /// </summary>
    public class World
    {
        /// <summary>
        /// A list of all drawable objects.
        /// </summary>
	    private GenericDrawableList drawableList;

        /// <summary>
        /// List of the HUD components.
        /// </summary>
        private List<InterfaceComponent> HUD;

	    private LevelScript script;

        /// <summary>
        /// The boundings of the game logic.
        /// </summary>
        private Bounding windowBounding = new Bounding(new Vector2(40, -160), 170f, 145f, true); 
        private Bounding visibleBounding = new Bounding(new Vector2(0, -240), 220f, 285f, false);
        private Bounding removeBounding = new Bounding(new Vector2(-60, -350), 500f, 520f, false);

        private TimeSpan endGame;
        private bool victory;

        public AchievementHandler achievementHandler;

        public event GlobalGameClasses.Eventhandler.GameEndEventHandler GameEnd;

        /// <summary>
        /// Adds a component to the HUD
        /// </summary>
        /// <param name="component">The given interface component.</param>
        public void addHUDComponent(InterfaceComponent component)
        {
            HUD.Add(component);
        }

        /// <summary>
        /// Gets all actors of a given type ans owner from the drawable list.
        /// </summary>
        /// <param name="T">the given type</param>
        /// <param name="owner">the given owner</param>
        /// <returns>a lsie of all actors of the given type and owner</returns>
	    public List<Actor> getActors(Type T, Owner owner)
	    {
            return drawableList.getActors(T, owner);
	    }

        /// <summary>
        /// Gets all actors from the drawable list.
        /// </summary>
        /// <returns>a list of all actors from the drawable list</returns>
	    public List<Actor> getActors()
	    {
            return drawableList.getActors();
	    }

        /// <summary>
        /// Adds an actor to the drawable list.
        /// </summary>
        /// <param name="actor">the given actor</param>
        /// <param name="canLeaveScreen">indicates whether the actor can leave the screen boundings</param>
	    public void addActor(Actor actor, bool canLeaveScreen)
	    {
            actor.Collide += achievementHandler.OnCollide;
            actor.Destroy += achievementHandler.OnDestroy;
            if (actor.owner != Owner.PLAYER && actor is Aircraft) 
            {
                actor.Destroy += OnDestroy_Aircraft;
            }
            if (actor is Aircraft)
            {
                ((Aircraft)actor).WeaponUpgrade += achievementHandler.OnUpgrade;
                if (((Aircraft)actor).aircraftAttributes.Contains(Aircraft.AircraftAttributes.Triggers_Victory))
                {
                    ((Aircraft)actor).Destroy += OnDestroy_Victory;
                }
                else if (((Aircraft)actor).aircraftAttributes.Contains(Aircraft.AircraftAttributes.Triggers_Defeat))
                {
                    ((Aircraft)actor).Destroy += OnDestroy_Defeat;
                } 
            }
            actor.world = this;
            if (!canLeaveScreen)
            {
                actor.worldBounding = windowBounding;
            }
            else if (actor is Projectile)
            {
                actor.worldBounding = visibleBounding;
            }
            else
            {
                actor.worldBounding = removeBounding;
            }
            actor.Initialize();
            drawableList.addActor(actor);
	    }

        /// <summary>
        /// Adds actors which can leave the boundings.
        /// </summary>
        /// <param name="actor"></param>
        public void addActor(Actor actor)
        {
            addActor(actor, true);
        }

        /// <summary>
        /// Adds a given list of actors.
        /// </summary>
        /// <param name="actors">The given list of actors.</param>
        public void addActor(List<Actor> actors)
        {
            foreach (Actor actor in actors)
            {
                addActor(actor);
            }
        }

        /// <summary>
        /// Remove a given actor from the drawable list.
        /// </summary>
        /// <param name="actor"></param>
	    public void removeActor(Actor actor)
	    {
            drawableList.removeActor(actor);
	    }

        
        /// <summary>
        /// Checks all actors for collissions with other actors.
        /// </summary>
	    private void checkAllCollissions()
	    {
            List<Actor> outerList;
            for (int i = 0; i < Owner.owners.Length; i++)
            {
                outerList = drawableList.getActors(typeof(Aircraft), Owner.owners[i]);
                for (int j = 0; j < Owner.owners.Length; j++)
                {
                    if (Owner.owners[i] != Owner.owners[j])
                    {
                        checkCollisions(outerList, drawableList.getActors(typeof(Projectile), Owner.owners[j]));
                        if (i < j)
                        {
                            checkCollisions(outerList, drawableList.getActors(typeof(Aircraft), Owner.owners[j]));
                        }
                    }
                }
                if (Owner.owners[i] == Owner.PLAYER)
                {
                    checkCollisions(outerList, drawableList.getActors(typeof(Item)));
                }
            }
	    }

        /// <summary>
        /// Checks for collisions between two given lists of actors.
        /// </summary>
        /// <param name="list1">the one list of actors</param>
        /// <param name="list2">the other list of actors</param>
        private void checkCollisions(List<Actor> list1, List<Actor> list2)
        {
            Actor[] innerList1 = new Actor[list1.Count];
            list1.CopyTo(innerList1);
            Actor[] innerList2 = new Actor[list2.Count];
            list2.CopyTo(innerList2);

            for (int i = innerList1.Length - 1; i >= 0; i--)
            {
                for (int j = innerList2.Length - 1; j >= 0; j--)
                {
                    if (isCollision(innerList1[i], innerList2[j]))
                    {
                        innerList1[i].collide(innerList2[j]);
                        innerList2[j].collide(innerList1[i]);
                    }
                }
            }
        }

        
        /// <summary>
        /// Determins wheter a collission between the two actors occured
        /// </summary>
        /// <param name="actor1">the one actor</param>
        /// <param name="actor2">the other actor</param>
        /// <returns></returns>
        private bool isCollision(Actor actor1, Actor actor2)
        {
            bool collision;
            BoundingSphere sphere1 = actor1.boundingSphere;
            BoundingSphere sphere2 = actor2.boundingSphere;
            collision = sphere1.Intersects(sphere2);
            collision |= sphere1.Contains(sphere2) == ContainmentType.Contains;
            collision |= sphere2.Contains(sphere1) == ContainmentType.Contains;

            return collision;
        }

        
        /// <summary>
        /// Returns the Actor of the defined type that is closest to the given position
        /// </summary>
        /// <param name="position">the given position</param>
        /// <param name="T">the given type</param>
        /// <returns>the closest element to a given position</returns>
        public Actor GetClosestElement(Vector2 position, Type T)
	    {
		   List<Actor> elements = drawableList.getActors(T);
           return GetClosestElement(position, elements);
	    }

        
        /// <summary>
        /// Returns the Actor of the defined type that is closest to the given position and has the specified owner
        /// </summary>
        /// <param name="position">the given position</param>
        /// <param name="T">the given type</param>
        /// <param name="owner">the given owner</param>
        /// <returns>the closest actor to the given position</returns>
        public Actor GetClosestElement(Vector2 position, Type T, Owner owner)
        {
            List<Actor> elements = drawableList.getActors(T, owner);
            return GetClosestElement(position, elements);
        }

        /// <summary>
        /// Returns the closest actor from a list of actors to a given position.
        /// </summary>
        /// <param name="position">the given position</param>
        /// <param name="elements">the given list of actors</param>
        /// <returns>the closest actor to a given position</returns>
        private Actor GetClosestElement(Vector2 position, List<Actor> elements)
        {
            float distance = float.MaxValue;
            float temp_distance;
            Vector2 temp_position;
            Actor closestElement = null;
            for (int i = 0; i < elements.Count; i++)
            {
                temp_position = elements[i].getGamePosition();
                Vector2.Distance(ref position, ref temp_position, out temp_distance);
                if (temp_distance < distance)
                {
                    distance = temp_distance;
                    closestElement = elements[i];
                }
            }
            return closestElement;
        }


        /// <summary>
        /// Gets all special effects of a defined type of the drawable list
        /// </summary>
        /// <param name="T">the given type</param>
        /// <returns>a list of all special effects of a given type</returns>
	    public List<SpecialEffect> getSpecialEffect(Type T)
	    {
            return drawableList.getSpecialEffects(T);
	    }

        /// <summary>
        /// Gets all special effects of the drawable list.
        /// </summary>
        /// <returns> a list of all special effects of the drawable list</returns>
	    public List<SpecialEffect> getSpecialEffects()
	    {
            return drawableList.getSpecialEffects();
	    }

        /// <summary>
        /// Adds a given special effect to the drawable list.
        /// </summary>
        /// <param name="specialEffect">the given special effect</param>
	    public virtual void addSpecialEffect(SpecialEffect specialEffect)
	    {
            drawableList.addSpecialEffect(specialEffect);
            specialEffect.worldBounding = removeBounding;
	    }

        /// <summary>
        /// The constructor of this class.
        /// </summary>
        public World(AchievementHandler achievementHandler, GenericDrawableList drawableList, List<InterfaceComponent> HUD, LevelScript script)
	    {
            this.achievementHandler = achievementHandler;
            this.drawableList = drawableList;
            this.HUD = HUD;
            this.script = script;
            this.endGame = TimeSpan.MinValue;
	    }

        /// <summary>
        /// Updates the gaming logic and take old and new keyboardstates
        /// </summary>
        /// <param name="oldKeyboardState">the old keyboardstate</param>
        /// <param name="newKeyboardState">the new keyboardstate</param>
	    public void Update(KeyboardState oldKeyboardState, KeyboardState newKeyboardState)
	    {
            moveCurrentScreen();
            removeDistantDrawables();
            checkAllCollissions();
            executeScript();
            updateActors(oldKeyboardState, newKeyboardState);
            if (endGame != TimeSpan.MinValue)
            {
                endGame -= GlobalGameValues.Values.gameTime.ElapsedGameTime;
                if (endGame <= TimeSpan.Zero)
                {
                    GameEnd(victory);
                }
            }
	    }

        
        /// <summary>
        /// Removes all DrawableObjects that have left the boundings of the world
        /// </summary>
        private void removeDistantDrawables() 
        {
            List<DrawableObject> drawableObjects = new List<DrawableObject>();
            drawableObjects.InsertRange(0,drawableList.getActors());
            drawableObjects.InsertRange(drawableObjects.Count, drawableList.getSpecialEffects());
            for (int i = drawableObjects.Count - 1; i >= 0; i--)
            {
                if (drawableObjects[i] is Actor && !isInBoundings(drawableObjects[i]))
                {
                    if (!((Actor)drawableObjects[i]).actorAttributes.Contains(Actor.ActorAttributes.Unremoveable)) 
                    {
                        ((Actor)drawableObjects[i]).removeFromWorld(true);
                    }
                }
                else if (drawableObjects[i] is SpecialEffect)
                {
                    if (((SpecialEffect)drawableObjects[i]).HasFinished() || !isInBoundings(drawableObjects[i]))
                    {
                        drawableList.removeSpecialEffect((SpecialEffect)drawableObjects[i]);
                    }
                }
            }
        }

        /// <summary>
        /// Checks whether a given drawable object is still in the boundings.
        /// </summary>
        /// <param name="drawableobject">The given drawable object</param>
        /// <returns>true, if the object is in the boundings, false otherwise</returns>
        private bool isInBoundings(DrawableObject drawableobject) 
        {
            bool isInBoundings = false;
            if (drawableobject is Actor)
            {
                isInBoundings = ((Actor)drawableobject).isInBoundings();
                isInBoundings |= ((Actor)drawableobject).worldBounding.isBinding;
            }
            else if (drawableobject is SpecialEffect)
            {
                isInBoundings = ((SpecialEffect)drawableobject).isInBoundings();
            }
            return isInBoundings;
        }

        
        /// <summary>
        /// Executes all pending events of the level script
        /// </summary>
        private void executeScript()
        {
            script.Update(GlobalGameClasses.GlobalGameValues.Values.gameTime);
            List<ScriptedEvent> pendingEvents = script.getPendingEvents();
            foreach(ScriptedEvent myEvent in pendingEvents) 
            {
                if (myEvent is Event_SetMusic) {
                    ((Event_SetMusic)myEvent).execute();
                }
                else if (myEvent is Event_CreateEnemyWave)
                {
                    ((Event_CreateEnemyWave)myEvent).execute(this, visibleBounding, removeBounding);
                }
                else if (myEvent is Event_CreateBoss)
                {
                    ((Event_CreateBoss)myEvent).execute(this, windowBounding, removeBounding);
                }
            }
        }

        
        /// <summary>
        /// Moves the game screen and all associated elements forward
        /// </summary>
        private void moveCurrentScreen()
        {
            float moveDistance = 0.5f * GlobalGameValues.SPEEDFACTOR * (float)GlobalGameValues.Values.gameTime.ElapsedGameTime.TotalMilliseconds;
            Vector2 moveVector = new Vector2(0, -moveDistance);
            removeBounding.moveBounding(moveVector);
            windowBounding.moveBounding(moveVector);
            visibleBounding.moveBounding(moveVector);
            List<Actor> Ships = drawableList.getActors(typeof(Aircraft));
            Vector2 newPosition;
            foreach (Actor aircraft in Ships)
            {
                if (aircraft.actorAttributes.Contains(DrawableObjects.Actors.Actor.ActorAttributes.Floating))
                {
                    newPosition = new Vector2(aircraft.worldMatrix.M41 + moveVector.X, aircraft.worldMatrix.M43 + moveVector.Y);
                    ((Aircraft)aircraft).setPosition(newPosition);
                }
            }
            List<Actor> Projectiles = drawableList.getActors(typeof(Projectile));
            foreach (Actor projectile in Projectiles)
            {
                if (projectile.actorAttributes.Contains(Actor.ActorAttributes.Floating))
                {
                    newPosition = new Vector2(projectile.worldMatrix.M41 + moveVector.X, projectile.worldMatrix.M43 + moveVector.Y);
                    ((Projectile)projectile).setPosition(newPosition);
                }
            }

        }

        
        /// <summary>
        /// Passes the Update call to all contained SpecialEffects
        /// </summary>
        private void updateSpecialEffects()
        {
            List<SpecialEffect> specialEffects = drawableList.getSpecialEffects();
            foreach (SpecialEffect effect in specialEffects)
            {
                effect.Update();
            }
        }

        
        /// <summary>
        /// Passes the Update call to all contained Actors, in case of Aircrafts also calls the corresponding aircraft controller.
        /// </summary>
        /// <param name="oldKeyboardState">The old keyboard state</param>
        /// <param name="newKeyboardState">The new keyboard state</param>
        private void updateActors(KeyboardState oldKeyboardState, KeyboardState newKeyboardState) 
        {
            List<Actor> actors = drawableList.getActors();
            AircraftController controller;
            DroneMode droneMode = getDroneMode(oldKeyboardState,newKeyboardState);
            List<AircraftCommand> aircraftCommands = getAircraftCommands(oldKeyboardState, newKeyboardState);
            foreach (Actor actor in actors)
            {
                if (actor is Projectile)
                {
                    ((Projectile)actor).Update();
                    if (actor is Missile)
                        actor.Update();
                }
                else if (actor is Item)
                {
                    ((Item)actor).Update();
                }
                else if (actor is Aircraft)
                {
                    controller = ((Aircraft)actor).controller;
                    if (controller is PlayerControlledAircraft) 
                    {
                        ((PlayerControlledAircraft)controller).Update(newKeyboardState);
                    } 
                    else if (controller is AIControlledAircraft_Drone)
                    {
                        ((AIControlledAircraft_Drone)controller).Update(droneMode);
                    }
                    else if (controller != null)
                    {
                        controller.Update();
                    }
                    actor.Update();
                } 
            }
        }

        
        /// <summary>
        /// Gets all commands for the PlayerControlled Aircrafts from the current Keyboard state
        /// </summary>
        /// <param name="oldKeyboardState">The old keyboard state</param>
        /// <param name="newKeyboardState">The new keyboard state</param>
        /// <returns>A list of all aircraft commands for the player controlled aircraft.</returns>
        private List<AircraftCommand> getAircraftCommands(KeyboardState oldKeyboardState, KeyboardState newKeyboardState)
        {
            List<AircraftCommand> commands = new List<AircraftCommand>();
            foreach (AircraftCommand command in AircraftCommand.aircraftCommands)
            {
                if (oldKeyboardState.IsKeyDown(command.standardKey) && newKeyboardState.IsKeyUp(command.standardKey))
                {
                    commands.Add(command);
                }
            }
            return commands;
        }

        
        /// <summary>
        /// Gets all commands for the AIControlled Drones from the current Keyboard state
        /// </summary>
        /// <param name="oldKeyboardState"> The old keyboard state</param>
        /// <param name="newKeyboardState">The new keyboard state</param>
        /// <returns>the drone mode, which contains all commands for the AIControlled Drones</returns>
        private DroneMode getDroneMode(KeyboardState oldKeyboardState, KeyboardState newKeyboardState)
        {
            int i = 0;
            DroneMode mode = null;
            while (mode == null && i < DroneMode.droneModes.Length)
            {
                if (oldKeyboardState.IsKeyDown(DroneMode.droneModes[i].standardKey) && newKeyboardState.IsKeyUp(DroneMode.droneModes[i].standardKey))
                {
                    mode = DroneMode.droneModes[i];
                }
                i++;
            }
            return mode;
        }

        /// <summary>
        /// Event which is thrown, when the player has destroyed the end boss.
        /// </summary>
        /// <param name="sender">the given destroyed actor </param>
        /// <param name="destroyedBy">the given actor which has destroyed the other actor</param>
	    private void OnDestroy_Victory(Actor sender, Actor destroyedBy)
	    {
            if (GameEnd != null)
            {
                victory = true;
                endGame = new TimeSpan(0, 0, 0, 1, 500);
            }
	    }

        /// <summary>
        /// Event which is thrown, when the player was destroyed by the end boss.
        /// </summary>
        /// <param name="sender">the given destroyed actor </param>
        /// <param name="destroyedBy">the given actor which has destroyed the other actor</param>
	    private void OnDestroy_Defeat(Actor sender, Actor destroyedBy)
	    {
            if (GameEnd != null)
            {
                victory = false;
                endGame = new TimeSpan(0, 0, 0, 1, 500);
            }
	    }

        /// <summary>
        /// Event which is thrown, when an aircraft was destroyed.
        /// </summary>
        /// <param name="sender">the given destroyed actor </param>
        /// <param name="destroyedBy">the given actor which has destroyed the other actor</param>
        private void OnDestroy_Aircraft(Actor sender, Actor destroyedBy)
        {
            Debug.Assert(sender is Aircraft, "Destroyed object was not an aircraft");
            GlobalGameClasses.GlobalGameValues.Values.score.value += (int)((float)((Aircraft)sender).killScore * GlobalGameValues.Values.difficulty.multiplier);
        }
    }
}

